#pragma once

#include <cstdint>
#include <random>
#include "../time_util.hh"

namespace kratos { namespace corelib {

// GUID
class Guid {
public:
    enum {
        // |  Timestamp  | Area ID | sequence |
        // |    42bit    |   10bit |   12bit  |
        // |---------------64bit--------------|
        CENTER = 1,

        // |  Timestamp  |  Seq ID  | Area ID | Logic ID |
        // |    32bit    |   16bit  |   10bit |     6    |
        // |----------------------64bit------------------|
        MULTI,
        
        // |  Random Number  |  Seq ID  | Area ID | Logic ID |
        // |       32bit     |   16bit  |   10bit |     6    |
        // |----------------------64bit----------------------|
        RANDOM,
    };

public:
    // ctor
    Guid()
        : _guid(0), _zone(0), _logic(0), _firstPart(0), _type(CENTER) {
    }

    // copy ctor
    Guid(const Guid& rht) {
        _guid = rht._guid;
        _type = rht._type;
        if (_type == CENTER) {
            _zone = (_guid & ZONE_MASK64) >> SEQUENCE_BITS;
        }
        else if (_type == MULTI) {
            _zone = (_guid & ZONE_MASK64_MULTI) >> LOGIC_BITS;
            _logic = (_guid & LOGIC_MASK64);
            _firstPart = _guid >> FIRSTPART_BITS_MULTI;
        }
    }

    // ctor
    // @param guid 64 bit GUID
    // @param type CENTER and MULTI
    Guid(uint64_t guid, int32_t type = CENTER)
        : _guid(guid) {
        _type = type;
        if (type == CENTER) {
            _zone = ((_guid & ZONE_MASK64) >> SEQUENCE_BITS);
            _logic = 0;
            _firstPart = 0;
        } else {
            _zone = ((_guid & ZONE_MASK64_MULTI) >> LOGIC_BITS);
            _logic = (_guid & LOGIC_MASK64);
            _firstPart = _guid >> FIRSTPART_BITS_MULTI;
        }
    }

    // dtor
    ~Guid() {
        _guid = 0;
        _zone = 0;
        _logic = 0;
        _firstPart = 0;
        _type = 0;
    }

    // Assignment
    // @param rht other instance
    // @return The reference of left side instance
    inline const Guid& operator=(const Guid& rht) {
        _guid = rht._guid;
        _type = rht._type;
        if (_type == CENTER) {
            _zone = ((_guid & ZONE_MASK64) >> SEQUENCE_BITS);
            _logic = 0;
            _firstPart = 0;
        }
        else {
            _zone = ((_guid & ZONE_MASK64_MULTI) >> LOGIC_BITS);
            _logic = (_guid & LOGIC_MASK64);
            _firstPart = _guid >> FIRSTPART_BITS_MULTI;
        }
        return *this;
    }

    // Equal test
    inline bool operator==(const Guid& rht) {
        return _guid == rht._guid;
    }

    // Equal test
    inline bool operator==(uint64_t guid) {
        return _guid == guid;
    }

    // 64 bit GUID
    // @return 64 bit GUID
    inline uint64_t get() const {
        return _guid;
    }

    /**
    * Returns zone ID
    * @return zone ID
    */
    inline uint32_t getZone() const {
        return _zone;
    }

    /**
    * Returns Logic ID for MULTI mode
    * @return Logic ID
    */
    inline uint32_t getLogic() const {
        return _logic;
    }

    /**
    * Returns type, CENTER or MULTI
    * @return type
    */
    inline int32_t getType() const {
        return _type;
    }

    /**
    * Returns timestamp for MULTI mode
    * @return timestamp for MULTI mode
    */
    inline int32_t getTimestamp() const {
        return _firstPart;
    }

    /**
    * Returns 64 bits GUID
    */
    operator uint64_t() {
        return _guid;
    }

    /**
    * Generates a GUID of CENTER mode
    * @param zone zone ID
    * @return GUID
    */
    static inline Guid generate(uint32_t zone) {
        Guid guid(GUIDCreate(zone));
        guid._zone = zone;
        return guid;
    }

    /**
    * Generates a GUID of MULTI mode
    * @param zone zone ID
    * @param logic logic ID
    * @return GUID
    */
    static inline Guid generate(uint32_t zone, uint32_t logic) {
        return Guid(GUIDCreate(zone, logic), MULTI);
    }

    /**
    * Generates a GUID of RANDOM mode
    * @param zone zone ID
    * @param logic logic ID
    * @return GUID
    */
    static inline Guid generateRandom(uint32_t zone, uint32_t logic) {
        return Guid(GUIDCreateRandom(zone, logic), RANDOM);
    }

private:
    /**
    * Returns the timestamp in millionseconds
    * @return The timestamp in millionseconds
    */
    static inline uint64_t _getTimestampMs() {
        return TimeUtil::getMillionSecondsTimestampOS();
    }

    /**
    * Generates GUID in CENTER mode
    * @param zone zone ID
    * @retval GUID
    * @retval 0 fail
    */
    static inline uint64_t GUIDCreate(uint32_t zone) {
        static uint32_t sequence = 0;                                  // The sequence ID in same millionsecond (0, 4095]
        static uint64_t lastTimestamp = _getTimestampMs() << STAMP_SHIFT_BITS; // Last timestamp  in millionsecond that the moment we generating GUID(64 - STAMP_SHIFT_BITS)
        uint64_t        guid = 0;                                  // GUID
        uint64_t        currentTimestamp = _getTimestampMs() << STAMP_SHIFT_BITS; // Last timestamp  in millionsecond (64 - STAMP_SHIFT_BITS)
        if (zone > ZONE_MASK) {
            return 0; // fail
        }
        if (currentTimestamp == lastTimestamp) {
            // In the same timestamp
            sequence += 1;
            if (sequence > SEQUENCE_MASK) {
                return 0; // fail
            }
        }
        else {
            // update last timestamp
            lastTimestamp = currentTimestamp;
            sequence = 1;
        }
        guid |= lastTimestamp;
        guid |= ((zone & ZONE_MASK) << SEQUENCE_BITS);
        guid |= sequence & SEQUENCE_MASK;
        return guid;
    }

    /**
    * Generates a GUID in MUTIL mode
    * @param zone zone ID
    * @param logic logic ID
    * @retval 64 bit GUID
    * @retval 0 fail
    */
    static inline uint64_t GUIDCreate(uint32_t zone, uint32_t logic) {
        static uint32_t sequence = 0;
        static uint64_t lastTimestamp = time(nullptr);
        uint64_t        guid = 0;
        uint64_t        currentTimestamp = time(nullptr);
        if (zone > ZONE_MASK_MULTI) {
            return 0;
        }
        if (currentTimestamp == lastTimestamp) {
            // Increase the sequence
            sequence += 1;
            if (sequence > SEQUENCE_MASK_MULTI) {
                return 0; // overflow
            }
        }
        else {
            // update the last timestamp
            lastTimestamp = currentTimestamp;
            sequence = 1;
        }
        guid |= (lastTimestamp << FIRSTPART_BITS_MULTI);
        guid |= ((sequence & SEQUENCE_MASK_MULTI) << SEQUENCE_BITS_MULTI);
        guid |= ((zone & ZONE_MASK_MULTI) << LOGIC_BITS);
        guid |= logic & LOGIC_MASK;
        return guid;
    }

    /**
    * Generates a GUID in RANDOM mode
    * @param zone zone ID
    * @param logic logic ID
    * @retval 64 bit GUID
    * @retval 0 fail
    */
    static inline uint64_t GUIDCreateRandom(uint32_t zone, uint32_t logic) {
        static std::default_random_engine e;
        static std::uint64_t randomNumber = e();
        static uint32_t sequence = 0;
        static uint64_t lastTimestamp = time(nullptr);
        uint64_t        guid = 0;
        uint64_t        currentTimestamp = time(nullptr);
        if (zone > ZONE_MASK_MULTI) {
            return 0;
        }
        if (currentTimestamp == lastTimestamp) {
            // Increase the sequence
            sequence += 1;
            if (sequence > SEQUENCE_MASK_MULTI) {
                return 0; // overflow
            }
        } else {
            // update the last timestamp
            lastTimestamp = currentTimestamp;
            sequence = 1;
            randomNumber = e();
        }
        guid |= (randomNumber << FIRSTPART_BITS_MULTI);
        guid |= ((sequence & SEQUENCE_MASK_MULTI) << SEQUENCE_BITS_MULTI);
        guid |= ((zone & ZONE_MASK_MULTI) << LOGIC_BITS);
        guid |= logic & LOGIC_MASK;
        return guid;
    }

    friend bool operator<(const Guid& a, const Guid& b);

private:
    uint64_t _guid;       ///< 64 bit GUID
    uint32_t _zone;       ///< zone ID
    uint32_t _logic;      ///< logic ID
    uint32_t _firstPart;  ///< High 32 BIT
    int32_t  _type;       ///< CENTER or MULTI
    static const uint64_t ZONE_MASK64 = 0x00000000003FF000;
    static const uint64_t ZONE_MASK64_MULTI = 0x000000000000FFC0;
    static const uint32_t ZONE_MASK = 0x3FF;
    static const uint32_t ZONE_MASK_MULTI = 0x3FF;
    static const uint32_t SEQUENCE_MASK = 0xFFF;
    static const uint32_t SEQUENCE_MASK_MULTI = 0xFFFF;
    static const uint32_t SEQUENCE_BITS_MULTI = 16;
    static const uint32_t SEQUENCE_BITS = 12;
    static const uint32_t STAMP_SHIFT_BITS = 22;
    static const uint32_t LOGIC_BITS = 6;
    static const uint64_t LOGIC_MASK64 = 0x000000000000003F;
    static const uint32_t LOGIC_MASK = 0x3F;
    static const uint32_t FIRSTPART_BITS_MULTI = 32;
};

/**
* std::map's comparator
* @param a GUID a
* @param b GUID b
* @retval true a < b
* @retval false a >= b
*/
inline bool operator<(const Guid& a, const Guid& b) {
    return a._guid < b._guid;
}

}}
